package file

import (
	"chainmaker.org/chainmaker/smarteditor/util"
	"errors"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"path/filepath"
	"strings"

	"chainmaker.org/chainmaker/smarteditor/container"
	"chainmaker.org/chainmaker/smarteditor/session"
	"github.com/88250/gulu"
)

const (
	//MaxFilesAmount max import file count
	MaxFilesAmount = 20
	//MaxFileSize import file max size
	MaxFileSize = 1024 * 1024 * 2 // 2M
	//FiveMB 5 MB
	FiveMB = 1024 * 1024 * 5 // 5M
)

var whiteList map[string]int

func init() {
	whiteList = initWhiteList()
	log.Debugf("impoter white list. %v", whiteList)
}

// ImportFileHandle handles request of import file
func ImportFileHandle(w http.ResponseWriter, r *http.Request) {
	defer util.PanicLog()

	uid, err := session.GetUidFromSession(r)
	if err != nil {
		log.Errorf("[ImportFileHandle] get uid fail. err:%v", err)
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	path := r.FormValue("path")
	if !session.CanAccess(uid, path) {
		log.Errorf("[ImportFileHandle] path is illegal. path:%s,err:%v", path, err)
		http.Error(w, "forbidden", http.StatusForbidden)
		return
	}

	result := gulu.Ret.NewResult()
	defer gulu.Ret.RetResult(w, r, result)

	err = r.ParseMultipartForm(32 << 20)
	if err != nil {
		log.Errorf("[ImportFileHandle] parse multipart form fial. err:%v", err)
		result.Code = -1
		result.Msg = err.Error()
		return
	}

	files := r.MultipartForm.File["fileupload"]
	length := len(files)
	log.Debugf("[ImportFileHandle], uid:%s, path:%s, file count:%d", uid, path, len(files))

	if length > MaxFilesAmount {
		result.Code = -1
		result.Msg = "warning: import too many files"
		log.Errorf("[ImportFileHandle] too many files, uid:%s, path:%s, file count:%d. max count:%d", uid, path, len(files), MaxFilesAmount)
		return
	}

	err = checkFileType(files, length)
	if err != nil {
		result.Code = -1
		result.Msg = err.Error()
		log.Errorf("[IportFileHandle] file type not support. err:%v", err)
		return
	}

	err = checkFileSize(files, length)
	if err != nil {
		result.Code = -1
		result.Msg = err.Error()
		log.Errorf("[IportFileHandle] file size reach limit. err:%v", err)
		return
	}

	//check file limit
	if container.SC.ResourceManager.OverFileCountLimitAfterAdd(uid, path, length) {
		result.Code = -1
		result.Msg = "file count reach limit"
		log.Errorf("[IportFileHandle] file count reach limit. err:%v", err)
		return
	}

	for i := 0; i < length; i++ {
		log.Debugf("[ImportFileHandle], uid:%s, path:%s, index:%d, file name:%s", uid, path, i, files[i].Filename)
		file, err := files[i].Open()
		if err != nil {
			result.Code = -1
			result.Msg = err.Error()
			log.Errorf("[ImportFileHandle] open request file fail, uid:%s, path:%s, index:%d, file name:%s, err:%v", uid, path, i, files[i].Filename, err)
			return
		}

		filePath := filepath.Join(path, files[i].Filename)
		localFile, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE, 0666)
		if err != nil {
			result.Code = -1
			result.Msg = err.Error()
			log.Errorf("[ImportFileHandle] open local file fail, uid:%s, index:%d, file path:%s, err:%v", uid, i, filePath, err)
			return
		}

		_, err = io.Copy(localFile, file)
		if err != nil {
			result.Code = -1
			result.Msg = err.Error()
			log.Errorf("[ImportFileHandle] write data to file fail, uid:%s, index:%d, file path:%s, err:%v", uid, i, filePath, err)
			return
		}
		err = container.SC.ResourceManager.AddFile(uid, path, 1)
		if err != nil {
			log.Errorf("[ImportFileHandle]  add file to resource mananger fail. err:%v", err)
		}
	}
}
func checkFileSize(files []*multipart.FileHeader, size int) error {
	for i := 0; i < size; i++ {
		if files[i].Size > MaxFileSize {
			return errors.New("file size reach limit")
		}
	}
	return nil
}
func checkFileType(files []*multipart.FileHeader, size int) error {
	for i := 0; i < size; i++ {
		if !isInWriteList(files[i].Filename, whiteList) {
			return errors.New("file type not supported. " + files[i].Filename)
		}
	}
	return nil
}
func initWhiteList() map[string]int {
	result := make(map[string]int)
	result[".go"] = 1
	result[".txt"] = 1
	result[".md"] = 1
	result[".mod"] = 1
	result[".sum"] = 1
	result[".java"] = 1
	result[".sol"] = 1
	result[".crt"] = 1
	result[".pem"] = 1
	result[".key"] = 1
	result[".yml"] = 1
	return result
}
func isInWriteList(fileName string, wList map[string]int) bool {
	for key, _ := range wList {
		if strings.Contains(fileName, key) {
			return true
		}
	}
	return false
}
